#include "sdk.h"
#include <tinyxml.h>
#include <wx/dirdlg.h>
#include <wx/display.h>
#include <wx/filefn.h>
#include <wx/fontmap.h>
#include <wx/msgdlg.h>
#include <wx/tokenzr.h>
#include <algorithm>
#include <string>
#include "filefilters.h"
#include "tinywxuni.h"
#include "filegroupsandmasks.h"
#include <unistd.h>
#include <sys/stat.h>

const wxString DEFAULT_WORKSPACE = _T( "default.workspace" );
const wxString DEFAULT_ARRAY_SEP = _T( ";" );
const wxString DEFAULT_CONSOLE_TERM = _T( "xterm -T $TITLE -e" );
const wxString DEFAULT_CONSOLE_SHELL = _T( "/bin/sh -c" );
const wxString cbDEFAULT_OPEN_FOLDER_CMD = _T( "filer" );

wxString GetStringFromArray( const wxArrayString& array, const wxString& separator, bool SeparatorAtEnd ) {
  wxString out;
  for( unsigned int i = 0; i < array.GetCount(); ++i ) {
    out << array[i];
    if( i < array.GetCount() - 1 || SeparatorAtEnd ) {
      out << separator;
    }
  }
  return out;
}

wxArrayString GetArrayFromString( const wxString& text, const wxString& separator, bool trimSpaces ) {
  wxArrayString out;
  wxString search = text;
  int seplen = separator.Length();
  while( true ) {
    int idx = search.Find( separator );
    if( idx == -1 ) {
      if( trimSpaces ) {
        search.Trim( false );
        search.Trim( true );
      }
      if( !search.IsEmpty() ) {
        out.Add( search );
      }
      break;
    }
    wxString part = search.Left( idx );
    search.Remove( 0, idx + seplen );
    if( trimSpaces ) {
      part.Trim( false );
      part.Trim( true );
    }
    if( !part.IsEmpty() ) {
      out.Add( part );
    }
  }
  return out;
}

wxStringVec GetVectorFromString( const wxString& text, const wxString& separator, bool trimSpaces ) {
  wxStringVec out;
  wxString search = text;
  int seplen = separator.Length();
  while( true ) {
    int idx = search.Find( separator );
    if( idx == -1 ) {
      if( trimSpaces ) {
        search.Trim( false );
        search.Trim( true );
      }
      if( !search.IsEmpty() ) {
        out.push_back( search );
      }
      break;
    }
    wxString part = search.Left( idx );
    search.Remove( 0, idx + seplen );
    if( trimSpaces ) {
      part.Trim( false );
      part.Trim( true );
    }
    if( !part.IsEmpty() ) {
      out.push_back( part );
    }
  }
  return out;
}

wxArrayString MakeUniqueArray( const wxArrayString& array, bool caseSens ) {
  wxArrayString out;
  for( size_t i = 0; i < array.GetCount(); ++i ) {
    if( caseSens ) {
      if( out.Index( array[i] ) == wxNOT_FOUND ) {
        out.Add( array[i] );
      }
    } else {
      if( out.Index( array[i].Lower() ) == wxNOT_FOUND ) {
        out.Add( array[i].Lower() );
      }
    }
  }
  return out;
}

wxString MakeUniqueString( const wxString& text, const wxString& separator, bool caseSens ) {
  return GetStringFromArray( MakeUniqueArray( GetArrayFromString( text, separator ), caseSens ), separator, false );
}

void AppendArray( const wxArrayString& from, wxArrayString& to ) {
  for( size_t i = 0; i < from.GetCount(); ++i ) {
    to.Add( from[i] );
  }
}

wxString UnixFilename( const wxString& filename, wxPathFormat format ) {
  wxString result = filename;
  if( format == wxPATH_NATIVE ) {
    format = wxPATH_UNIX;
  }
  bool unc_name = result.StartsWith( _T( "\\\\" ) );
  if( format == wxPATH_WIN || unc_name ) {
    result.Replace( _T( "/" ), _T( "\\" ) );
    while( result.Replace( _T( "\\\\" ), _T( "\\" ) ) ) ;
    if( unc_name ) {
      result.Prepend( _T( "\\" ) );
    }
  } else {
    result.Replace( _T( "\\" ), _T( "/" ) );
    while( result.Replace( _T( "//" ), _T( "/" ) ) ) ;
  }
  return result;
}

void QuoteStringIfNeeded( wxString& str ) {
  if( NeedQuotes( str ) ) {
    str = wxString( _T( "\"" ) ) + str + _T( "\"" );
  }
}

bool NeedQuotes( const wxString &str ) {
  bool hasSpace = str.Find( _T( ' ' ) ) != -1;
  bool hasParen = true && ( str.Find( _T( '(' ) ) != -1 || str.Find( _T( ')' ) ) != -1 );
  return !str.IsEmpty() && str.GetChar( 0 ) != _T( '"' ) && ( hasSpace || hasParen );
}

wxString EscapeSpaces( const wxString& str ) {
  wxString ret = str;
  if( !ret.IsEmpty() && ret[0] != _T( '"' ) && ret[0] != _T( '\'' ) ) {
    ret.Replace( _T( " " ), _T( "\\ " ) );
    ret.Replace( _T( "\t" ), _T( "\\\t" ) );
  }
  return ret;
}

FileType FileTypeOf( const wxString& filename ) {
  wxString ext = filename.AfterLast( _T( '.' ) ).Lower();
  if( ext.IsSameAs( FileFilters::ASM_EXT ) ||
      ext.IsSameAs( FileFilters::C_EXT ) ||
      ext.IsSameAs( FileFilters::CC_EXT ) ||
      ext.IsSameAs( FileFilters::CPP_EXT ) ||
      ext.IsSameAs( FileFilters::CXX_EXT ) ||
      ext.IsSameAs( FileFilters::S_EXT ) ||
      ext.IsSameAs( FileFilters::SS_EXT ) ||
      ext.IsSameAs( FileFilters::S62_EXT ) ) {
    return ftSource;
  } else if( ext.IsSameAs( FileFilters::TPP_EXT ) ) {
    return ftTemplateSource;
  } else if( ext.IsSameAs( FileFilters::H_EXT ) ||
             ext.IsSameAs( FileFilters::HH_EXT ) ||
             ext.IsSameAs( FileFilters::HPP_EXT ) ||
             ext.IsSameAs( FileFilters::HXX_EXT ) ||
             ext.IsSameAs( FileFilters::INL_EXT ) ) {
    return ftHeader;
  } else if( ext.IsSameAs( FileFilters::M_CODE_EXT ) ) {
    return ftm_Code_Project;
  } else if( ext.IsSameAs( FileFilters::M_WORKSPACE_EXT ) ) {
    return ftm_Code_Workspace;
  }
  if( ext.IsSameAs( FileFilters::OBJECT_EXT ) ) {
    return ftObject;
  } else if( ext.IsSameAs( FileFilters::XRCRESOURCE_EXT ) ) {
    return ftXRCResource;
  } else if( ext.IsSameAs( FileFilters::RESOURCE_EXT ) ) {
    return ftResource;
  } else if( ext.IsSameAs( FileFilters::RESOURCEBIN_EXT ) ) {
    return ftResourceBin;
  } else if( ext.IsSameAs( FileFilters::STATICLIB_EXT ) ) {
    return ftStaticLib;
  } else if( ext.IsSameAs( FileFilters::DYNAMICLIB_EXT ) ) {
    return ftDynamicLib;
  } else if( ext.IsSameAs( FileFilters::NATIVE_EXT ) ) {
    return ftNative;
  } else if( ext.IsSameAs( FileFilters::EXECUTABLE_EXT ) ) {
    return ftExecutable;
  } else if( ext.IsSameAs( FileFilters::XML_EXT ) ) {
    return ftXMLDocument;
  } else if( ext.IsSameAs( FileFilters::SCRIPT_EXT ) ) {
    return ftScript;
  } else {
    ProjectManager *prjMgr = Manager::Get()->GetProjectManager();
    if( prjMgr ) {
      const FilesGroupsAndMasks* fgm = prjMgr->GetFilesGroupsAndMasks();
      if( fgm ) {
        for( unsigned int i = 0; i != fgm->GetGroupsCount(); ++i ) {
          if( fgm->GetGroupName( i ) == _T( "Sources" ) && fgm->MatchesMask( ext, i ) ) {
            return ftSource;
          }
          if( fgm->GetGroupName( i ) == _T( "Headers" ) && fgm->MatchesMask( ext, i ) ) {
            return ftHeader;
          }
        }
      }
    }
  }
  return ftOther;
}

wxString cbFindFileInPATH( const wxString &filename ) {
  wxString pathValues;
  wxGetEnv( _T( "PATH" ), &pathValues );
  if( pathValues.empty() ) {
    return wxEmptyString;
  }
  const wxString &sep =  _T( ":" );
  wxChar pathSep = wxFileName::GetPathSeparator();
  const wxArrayString &pathArray = GetArrayFromString( pathValues, sep );
  for( size_t i = 0; i < pathArray.GetCount(); ++i ) {
    if( wxFileExists( pathArray[i] + pathSep + filename ) ) {
      if( pathArray[i].AfterLast( pathSep ).IsSameAs( _T( "bin" ) ) ) {
        return pathArray[i];
      }
    }
  }
  return wxEmptyString;
}

void DoRememberSelectedNodes( wxTreeCtrl* tree, wxArrayString& selectedItemPaths ) {
  wxArrayTreeItemIds items;
  if( tree->GetSelections( items ) < 1 ) {
    return;
  }
  for( size_t i = 0; i < items.GetCount(); ++i ) {
    wxString path = wxEmptyString;
    wxTreeItemId item = items[i];
    while( item.IsOk() ) {
      path = _T( "/" ) + tree->GetItemText( item ) + path;
      item = tree->GetItemParent( item );
    }
    if( path != wxEmptyString ) {
      selectedItemPaths.Add( path );
    }
  }
}

void DoSelectRememberedNode( wxTreeCtrl* tree, const wxTreeItemId& parent, wxString& selectedItemPath ) {
  if( tree && !selectedItemPath.IsEmpty() ) {
    wxString tmpPath;
    wxString folder;
    tmpPath = selectedItemPath;
    int pos = tmpPath.Find( _T( '/' ) );
    while( pos == 0 ) {
      tmpPath = tmpPath.Right( tmpPath.Length() - pos - 1 );
      pos = tmpPath.Find( _T( '/' ) );
    }
    folder = tmpPath.Left( pos );
    tmpPath = tmpPath.Right( tmpPath.Length() - pos - 1 );
    wxTreeItemId item = parent;
    wxTreeItemIdValue cookie = nullptr;
    while( item.IsOk() ) {
      if( tree->GetItemText( item ) != folder ) {
        item = tree->GetNextSibling( item );
      } else {
        if( pos < 0 ) {
          tree->SelectItem( item );
          break;
        } else {
          item = tree->GetNextChild( item, cookie );
          DoSelectRememberedNode( tree, item, tmpPath );
        }
      }
    }
  }
}

bool DoRememberExpandedNodes( wxTreeCtrl* tree, const wxTreeItemId& parent, wxArrayString& nodePaths, wxString& path ) {
  if( !tree || !parent.IsOk() ) {
    return false;
  }
  wxString originalPath = path;
  bool found = false;
  wxTreeItemIdValue cookie = nullptr;
  wxTreeItemId child = tree->GetFirstChild( parent, cookie );
  while( child.IsOk() ) {
    if( tree->ItemHasChildren( child ) && tree->IsExpanded( child ) ) {
      found = true;
      path << _T( "/" ) << tree->GetItemText( child );
      DoRememberExpandedNodes( tree, child, nodePaths, path );
      nodePaths.Add( path );
      path = originalPath;
    }
    child = tree->GetNextChild( parent, cookie );
  }
  return found;
}

void DoExpandRememberedNode( wxTreeCtrl* tree, const wxTreeItemId& parent, const wxString& path ) {
  if( !path.IsEmpty() ) {
    wxString tmpPath;
    tmpPath = path;
    wxString folder;
    int pos = tmpPath.Find( _T( '/' ) );
    while( pos == 0 ) {
      tmpPath = tmpPath.Right( tmpPath.Length() - pos - 1 );
      pos = tmpPath.Find( _T( '/' ) );
    }
    if( pos < 0 ) {
      folder = tmpPath;
      tmpPath.Clear();
    } else {
      folder = tmpPath.Left( pos );
      tmpPath = tmpPath.Right( tmpPath.Length() - pos - 1 );
    }
    wxTreeItemIdValue cookie = nullptr;
    wxTreeItemId child = tree->GetFirstChild( parent, cookie );
    while( child.IsOk() ) {
      wxString itemText = tree->GetItemText( child );
      if( itemText.Matches( folder ) ) {
        tree->Expand( child );
        DoExpandRememberedNode( tree, child, tmpPath );
        break;
      }
      child = tree->GetNextChild( parent, cookie );
    }
  }
}

void SaveTreeState( wxTreeCtrl* tree, const wxTreeItemId& parent, wxArrayString& nodePaths, wxArrayString& selectedItemPaths ) {
  nodePaths.Clear();
  if( !parent.IsOk() || !tree || !tree->ItemHasChildren( parent ) || !tree->IsExpanded( parent ) ) {
    return;
  }
  wxString tmp;
  if( !DoRememberExpandedNodes( tree, parent, nodePaths, tmp ) ) {
    nodePaths.Add( tmp );
  }
  selectedItemPaths.Clear();
  DoRememberSelectedNodes( tree, selectedItemPaths );
}

void RestoreTreeState( wxTreeCtrl* tree, const wxTreeItemId& parent, wxArrayString& nodePaths, wxArrayString& selectedItemPaths ) {
  if( !parent.IsOk() || !tree ) {
    return;
  }
  if( nodePaths.GetCount() == 0 ) {
    tree->Collapse( parent );
    return;
  }
  for( unsigned int i = 0; i < nodePaths.GetCount(); ++i ) {
    DoExpandRememberedNode( tree, parent, nodePaths[i] );
  }
  nodePaths.Clear();
  for( unsigned int i = 0; i < selectedItemPaths.GetCount(); ++i ) {
    DoSelectRememberedNode( tree, tree->GetRootItem(), selectedItemPaths[i] );
  }
  selectedItemPaths.Clear();
}

bool CreateDirRecursively( const wxString& full_path, int perms ) {
  wxFileName tmp( full_path );
  if( wxDirExists( tmp.GetPath() ) ) {
    return true;
  }
  wxArrayString dirs;
  wxString currdir;
  currdir = tmp.GetVolume() + tmp.GetVolumeSeparator() + wxFILE_SEP_PATH;
  dirs = tmp.GetDirs();
  for( size_t i = 0; i < dirs.GetCount(); ++i ) {
    currdir << dirs[i];
    if( !wxDirExists( currdir ) && !wxMkdir( currdir, perms ) ) {
      return false;
    }
    currdir << wxFILE_SEP_PATH;
  }
  return true;
}

bool CreateDir( const wxString& full_path, int perms ) {
  if( !wxDirExists( full_path ) && !wxMkdir( full_path, perms ) ) {
    return false;
  }
  return true;
}

wxString ChooseDirectory( wxWindow* parent, const wxString& message, const wxString& initialPath, const wxString& basePath,
                          bool askToMakeRelative, bool showCreateDirButton ) {
  wxDirDialog dlg( parent, message, _T( "" ), ( showCreateDirButton ? wxDD_NEW_DIR_BUTTON : 0 ) | wxRESIZE_BORDER );
  dlg.SetPath( initialPath );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() != wxID_OK ) {
    return wxEmptyString;
  }
  wxFileName path( dlg.GetPath() );
  if( askToMakeRelative && !basePath.IsEmpty() ) {
    if( msging( _T( "将此作为相对路径?" ), _T( "Question" ), wxICON_QUESTION | wxYES_NO ) == wxID_YES ) {
      path.MakeRelativeTo( basePath );
    }
  }
  return path.GetFullPath();
}


bool cbRead( wxFile& file, wxString& st, wxFontEncoding encoding ) {
  st.Empty();
  if( !file.IsOpened() ) {
    return false;
  }
  int len = file.Length();
  if( !len ) {
    file.Close();
    return true;
  }
  char* buff = new char[len + 1];
  if( !buff ) {
    file.Close();
    return false;
  }
  file.Read( ( void* )buff, len );
  file.Close();
  buff[len] = '\0';
  DetectEncodingAndConvert( buff, st, encoding );
  delete [] buff;
  return true;
}

wxString cbReadFileContents( wxFile& file, wxFontEncoding encoding ) {
  wxString st;
  cbRead( file, st, encoding );
  return st;
}

bool cbWrite( wxFile& file, const wxString& buff, wxFontEncoding encoding ) {
  bool result = false;
  if( file.IsOpened() ) {
    wxCSConv conv( encoding );
    result = file.Write( buff, conv );
    if( result ) {
      file.Flush();
    }
    file.Close();
  }
  return result;
}

bool cbSaveToFile( const wxString& filename, const wxString& contents, wxFontEncoding encoding, bool bom ) {
  return Manager::Get()->GetFileManager()->Save( filename, contents, encoding, bom );
}

bool cbSaveTinyXMLDocument( TiXmlDocument* doc, const wxString& filename ) {
  return TinyXML::SaveDocument( filename, doc );
}

wxString cbC2U( const char* str ) {
  return wxString( str, wxConvUTF8 );
}

const wxWX2MBbuf cbU2C( const wxString& str ) {
  return str.mb_str( wxConvUTF8 );
}

wxFontEncoding DetectEncodingAndConvert( const char* strIn, wxString& strOut, wxFontEncoding possibleEncoding ) {
  wxFontEncoding encoding = possibleEncoding;
  strOut.Clear();
  if( possibleEncoding != wxFONTENCODING_UTF16 &&
      possibleEncoding != wxFONTENCODING_UTF16LE &&
      possibleEncoding != wxFONTENCODING_UTF16BE &&
      possibleEncoding != wxFONTENCODING_UTF32 &&
      possibleEncoding != wxFONTENCODING_UTF32LE &&
      possibleEncoding != wxFONTENCODING_UTF32BE ) {
    wxCSConv conv( possibleEncoding );
    strOut = wxString( strIn, conv );
    if( strOut.Length() == 0 ) {
      if( possibleEncoding != wxFONTENCODING_UTF8 ) {
        encoding = wxFONTENCODING_UTF8;
        strOut = wxString( strIn, wxConvUTF8 );
      }
      if( strOut.Length() == 0 ) {
        for( int i = wxFONTENCODING_SYSTEM; i < wxFONTENCODING_ISO8859_MAX; ++i ) {
          encoding = ( wxFontEncoding )i;
          if( encoding == possibleEncoding ) {
            continue;
          }
          wxCSConv csconv( encoding );
          strOut = wxString( strIn, csconv );
          if( strOut.Length() != 0 ) {
            break;
          }
        }
      }
    }
  } else {
    strOut = ( const wxChar* ) strIn;
  }
  return encoding;
}

wxString URLEncode( const wxString &str ) {
  wxString ret;
  wxString t;
  for( unsigned int i = 0; i < str.length(); ++i ) {
    wxChar c = str[i];
    if( ( c >= _T( 'A' ) && c <= _T( 'Z' ) )
        || ( c >= _T( 'a' ) && c <= _T( 'z' ) )
        || ( c >= _T( '0' ) && c <= _T( '9' ) )
        || ( c == _T( '.' ) )
        || ( c == _T( '-' ) )
        || ( c == _T( '_' ) ) ) {
      ret.Append( c );
    } else if( c == _T( ' ' ) ) {
      ret.Append( _T( '+' ) );
    } else {
      t.sprintf( _T( "%%%02X" ), ( unsigned int ) c );
      ret.Append( t );
    }
  }
  return ret;
}

typedef std::map<wxString, wxString> BackticksMap;
BackticksMap m_Backticks;
wxString ExpandBackticks( wxString& str ) {
  wxString ret;
  size_t start = str.find( _T( '`' ) );
  if( start == wxString::npos ) {
    return ret;
  }
  size_t end = str.find( _T( '`' ), start + 1 );
  if( end == wxString::npos ) {
    return ret;
  }
  while( start != wxString::npos && end != wxString::npos ) {
    wxString cmd = str.substr( start + 1, end - start - 1 );
    cmd.Trim( true );
    cmd.Trim( false );
    if( cmd.IsEmpty() ) {
      break;
    }
    wxString bt;
    BackticksMap::iterator it = m_Backticks.find( cmd );
    if( it != m_Backticks.end() ) {
      bt = it->second;
    } else {
      Manager::Get()->GetLogManager()->DebugLog( F( _T( "缓存结果 `%s`" ), cmd.wx_str() ) );
      wxArrayString output;
      wxExecute( cmd, output, wxEXEC_NODISABLE );
      bt = GetStringFromArray( output, _T( " " ), false );
      m_Backticks[cmd] = bt;
      Manager::Get()->GetLogManager()->DebugLog( _T( "Cached" ) );
    }
    ret << bt << _T( ' ' );
    str = str.substr( 0, start ) + bt + str.substr( end + 1, wxString::npos );
    start = str.find( _T( '`' ) );
    end = str.find( _T( '`' ), start + 1 );
  }
  return ret;
}

wxMenu* CopyMenu( wxMenu* mnu, bool with_accelerators ) {
  if( !mnu || mnu->GetMenuItemCount() < 1 ) {
    return nullptr;
  }
  wxMenu* theMenu = new wxMenu();
  for( size_t i = 0; i < mnu->GetMenuItemCount(); ++i ) {
    wxMenuItem* tmpItem = mnu->FindItemByPosition( i );
    wxMenuItem* theItem = new wxMenuItem( nullptr, tmpItem->GetId(), with_accelerators ? tmpItem->GetItemLabel() : tmpItem->GetItemLabelText(),
                                          tmpItem->GetHelp(), tmpItem->GetKind(), CopyMenu( tmpItem->GetSubMenu() ) );
    theMenu->Append( theItem );
  }
  return theMenu;
}

bool IsWindowReallyShown( wxWindow* win ) {
  while( win && win->IsShown() ) {
    win = win->GetParent();
    if( !win ) {
      return true;
    }
  }
  return false;
}

bool NormalizePath( wxFileName& f, const wxString& base ) {
  bool result = true;
  f.Normalize( wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base );
  result = f.IsOk();
  return result;
}

bool IsSuffixOfPath( wxFileName const & suffix, wxFileName const & path ) {
  if( path.GetFullName() != suffix.GetFullName() ) {
    return false;
  }
  wxArrayString suffixDirArray = suffix.GetDirs();
  wxArrayString pathDirArray = path.GetDirs();
  int j = pathDirArray.GetCount() - 1;
  for( int i = suffixDirArray.GetCount() - 1; i >= 0; i-- ) {
    if( suffixDirArray[i] == _T( "." ) || suffixDirArray[i] == _T( "" ) ) {
      continue;
    }
    if( j < 0 ) {
      return false;
    }
    if( suffixDirArray[i] == _T( ".." ) ) {
      return true;
    } else if( suffixDirArray[i] != pathDirArray[j] ) {
      return false;
    }
    j--;
  }
  if( suffix.IsAbsolute() && ( j >= 0 || suffix.GetVolume() != path.GetVolume() ) ) {
    return false;
  }
  return true;
}

bool cbResolveSymLinkedDirPath( wxString& dirpath ) {
  if( dirpath.Last() == wxFILE_SEP_PATH ) {
    dirpath.RemoveLast();
  }
  struct stat fileStats;
  if( lstat( dirpath.mb_str( wxConvUTF8 ), &fileStats ) != 0 ) {
    return false;
  }
  if( S_ISLNK( fileStats.st_mode ) ) {
    char buffer[4096];
    int result = readlink( dirpath.mb_str( wxConvUTF8 ), buffer, WXSIZEOF( buffer ) - 1 );
    if( result != -1 ) {
      buffer[result] = '\0';
      wxString pathStr( buffer, wxConvUTF8 );
      wxFileName fileName = wxFileName::DirName( pathStr );
      if( !fileName.IsAbsolute() ) {
        wxFileName dirNamePath;
        if( dirpath.Last() == wxFILE_SEP_PATH ) {
          dirNamePath = wxFileName::DirName( dirpath );
        } else {
          dirNamePath = wxFileName::DirName( dirpath + wxFILE_SEP_PATH );
        }
        dirNamePath.RemoveLastDir();
        fileName.MakeAbsolute( dirNamePath.GetFullPath() );
      }
      wxString fullPath = fileName.GetFullPath();
      if( fullPath.Last() == _T( '.' ) ) {
        fullPath.RemoveLast();
      }
      if( fullPath.Last() == wxFILE_SEP_PATH ) {
        fullPath.RemoveLast();
      }
      dirpath = fullPath;
      return true;
    }
  }
  return false;
}

wxString cbResolveSymLinkedDirPathRecursive( wxString dirpath ) {
  while( cbResolveSymLinkedDirPath( dirpath ) ) ;
  return dirpath;
}

bool UsesCommonControls6() {
  return true;
}

wxBitmap M_Code_LoadBitmap( const wxString& filename, wxBitmapType bitmapType ) {
  static bool oldCommonControls = !UsesCommonControls6();
  wxImage im;
  wxFileSystem* fs = new wxFileSystem;
  wxFSFile* f = fs->OpenFile( filename );
  if( f ) {
    wxInputStream* is = f->GetStream();
    im.LoadFile( *is, bitmapType );
    delete f;
  }
  delete fs;
  if( oldCommonControls && im.HasAlpha() ) {
    im.ConvertAlphaToMask();
  }
  return wxBitmap( im );
}

void SetSettingsIconsStyle( M_Code_unused wxListCtrl* lc, M_Code_unused SettingsIconsStyle style ) {
}

SettingsIconsStyle GetSettingsIconsStyle( M_Code_unused wxListCtrl* lc ) {
  return GetSettingsIconsStyle();
}

SettingsIconsStyle GetSettingsIconsStyle() {
  return SettingsIconsStyle( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadInt( _T( "/environment/settings_size" ), 0 ) );
}

void PlaceWindow( wxTopLevelWindow *w, cbPlaceDialogMode mode, bool enforce ) {
  if( !w ) {
    m_Throw( _T( "非法传递了空指针." ) );
  }
  ConfigManager *cfg = Manager::Get()->GetConfigManager( _T( "app" ) );
  if( !enforce && cfg->ReadBool( _T( "/dialog_placement/do_place" ) ) == false ) {
    return;
  }
  wxWindow* referenceWindow = Manager::Get()->GetAppWindow();
  if( !referenceWindow ) {
    referenceWindow = w;
  }
  int the_mode;
  if( mode == pdlBest ) {
    the_mode = ( int ) pdlCentre;
  } else {
    the_mode = ( int ) mode;
  }
  wxRect monitorRect;
  if( wxDisplay::GetCount() > 0 ) {
    int displayIdx = wxDisplay::GetFromWindow( referenceWindow );
    if( displayIdx == wxNOT_FOUND ) {
      displayIdx = 0;
    }
    wxDisplay display( displayIdx );
    monitorRect = display.GetClientArea();
    monitorRect = display.GetGeometry().Intersect( monitorRect );
  } else {
    int width, height;
    wxDisplaySize( &width, &height );
    monitorRect = wxRect( 0, 0, width, height );
  }
  wxRect windowRect = w->GetRect();
  switch( the_mode ) {
    case pdlCentre: {
      windowRect.x = monitorRect.x + ( monitorRect.width  - windowRect.width ) / 2;
      windowRect.y = monitorRect.y  + ( monitorRect.height - windowRect.height ) / 2;
    }
    break;
    case pdlHead: {
      windowRect.x = monitorRect.x + ( monitorRect.width  - windowRect.width ) / 2;
      windowRect.y = monitorRect.y  + ( monitorRect.height - windowRect.height ) / 3;
    }
    break;
    case pdlConstrain: {
      int x1 = windowRect.x;
      int x2 = windowRect.x + windowRect.width;
      int y1 = windowRect.y;
      int y2 = windowRect.y + windowRect.height;
      if( windowRect.width > monitorRect.width ) {
        x1 = monitorRect.x + ( monitorRect.width  - windowRect.width ) / 2;
        x2 = x1 + windowRect.width;
      } else {
        x2 = std::min( monitorRect.GetRight(), windowRect.GetRight() );
        x1 = std::max( x2 - windowRect.width, monitorRect.x );
        x2 = x1 + windowRect.width;
      }
      if( windowRect.height > monitorRect.height ) {
        y1 = monitorRect.y + ( monitorRect.height  - windowRect.height ) / 2;
        y2 = y1 + windowRect.height;
      } else {
        y2 = std::min( monitorRect.GetBottom(), windowRect.GetBottom() );
        y1 = std::max( y2 - windowRect.height, monitorRect.y );
        y2 = y1 + windowRect.height;
      }
      windowRect = wxRect( x1, y1, x2 - x1, y2 - y1 );
    }
    break;
    case pdlClip: {
      int x1 = windowRect.x;
      int x2 = windowRect.x + windowRect.width;
      int y1 = windowRect.y;
      int y2 = windowRect.y + windowRect.height;
      x1 = std::max( x1, monitorRect.x );
      x2 = std::min( x2, monitorRect.GetRight() );
      y1 = std::max( y1, monitorRect.y );
      y2 = std::min( y2, monitorRect.GetBottom() );
      windowRect = wxRect( x1, y1, x2 - x1, y2 - y1 );
    }
    break;
  }
  w->SetSize( windowRect.x,  windowRect.y, windowRect.width, windowRect.height, wxSIZE_ALLOW_MINUS_ONE );
}

DirAccessCheck cbDirAccessCheck( const wxString& dir ) {
  wxString actualDir = dir;
  if( actualDir.Last() != _T( '/' ) && actualDir.Last() != _T( '\\' ) ) {
    actualDir << wxFILE_SEP_PATH;
  }
  if( !wxDirExists( actualDir ) ) {
    return dacInvalidDir;
  }
  wxString testFile = wxFileName::CreateTempFileName( actualDir );
  if( !testFile.IsEmpty() ) {
    wxRemoveFile( testFile );
    return dacReadWrite;
  }
  return dacReadOnly;
}

wxString realpath( const wxString& path ) {
  char buf[2048] = {};
  struct stat buffer;
  std::string ret = ( const char* )cbU2C( path );
  size_t lastPos = 0;
  size_t slashPos = ret.find( '/', lastPos );
  while( slashPos != std::string::npos ) {
    if( lstat( ret.substr( 0, slashPos ).c_str(), &buffer ) == 0 ) {
      if( S_ISLNK( buffer.st_mode ) ) {
        int s = readlink( ret.substr( 0, slashPos ).c_str(), buf, sizeof( buf ) );
        buf[s] = 0;
        if( s > 0 && buf[0] != '/' && buf[0] != '~' ) {
          ret = ret.substr( 0, lastPos ) + buf + ret.substr( slashPos, ret.size() - slashPos );
        } else {
          ret = buf + ret.substr( slashPos, ret.size() - slashPos );
          s = 0;
        }
        slashPos = s;
      }
    }
    while( ret[++slashPos] == '/' ) ;
    lastPos = slashPos;
    slashPos = ret.find( '/', slashPos );
  }
  return cbC2U( ret.c_str() );
}

int msging( const wxString& message, const wxString& caption, int style, wxWindow *parent, int x, int y ) {
  if( !parent ) {
    parent = Manager::Get()->GetAppWindow();
  }
  if( !parent ) {
    int answer = wxMessageBox( message, caption, style, parent, x, y );
    switch( answer ) {
      case( wxOK ):
        return wxID_OK;
      case( wxCANCEL ):
        return wxID_CANCEL;
      case( wxYES ):
        return wxID_YES;
      case( wxNO ):
        return wxID_NO;
      default:
        return -1;
    }
  }
  wxMessageDialog dlg( parent, message, caption, style, wxPoint( x, y ) );
  PlaceWindow( &dlg );
  return dlg.ShowModal();
}

int cbGetSingleChoiceIndex( const wxString& message, const wxString& caption, const wxArrayString& choices, wxWindow *parent,
                            const wxSize &size, int initialSelection ) {
  if( !parent ) {
    parent = Manager::Get()->GetAppWindow();
  }
  wxSingleChoiceDialog dialog( parent, message, caption, choices );
  dialog.SetSelection( initialSelection );
  dialog.SetSize( size );
  PlaceWindow( &dialog );
  return ( dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1 );
}

wxArrayInt cbGetMultiChoiceDialog( const wxString& message, const wxString& caption, const wxArrayString& choices, wxWindow *parent,
                                   const wxSize& size, const wxArrayInt& initialSelection ) {
  if( !parent ) {
    parent = Manager::Get()->GetAppWindow();
  }
  wxMultiChoiceDialog dialog( parent, message, caption, choices );
  dialog.SetSelections( initialSelection );
  dialog.SetSize( size );
  PlaceWindow( &dialog );
  if( dialog.ShowModal() == wxID_OK ) {
    return dialog.GetSelections();
  } else {
    return wxArrayInt();
  }
}

const wxChar* cbGetTextFromUserPromptStr = wxGetTextFromUserPromptStr;

wxString cbGetTextFromUser( const wxString& message, const wxString& caption, const wxString& defaultValue,
                            wxWindow *parent, wxCoord x, wxCoord y, bool centre ) {
  if( !parent ) {
    parent = Manager::Get()->GetAppWindow();
  }
  long style = wxTextEntryDialogStyle;
  if( centre ) {
    style |= wxCENTRE;
  } else {
    style &= ~wxCENTRE;
  }
  wxTextEntryDialog dialog( parent, message, caption, defaultValue, style, wxPoint( x, y ) );
  PlaceWindow( &dialog );
  wxString str;
  if( dialog.ShowModal() == wxID_OK ) {
    str = dialog.GetValue();
  }
  return str;
}

wxImageList* cbProjectTreeImages::MakeImageList() {
  static const wxString imgs[] = {
    _T( "file.png" ),
    _T( "file-missing.png" ),
    _T( "file-modified.png" ),
    _T( "file-readonly.png" ),
    _T( "rc-file-added.png" ),
    _T( "rc-file-conflict.png" ),
    _T( "rc-file-missing.png" ),
    _T( "rc-file-modified.png" ),
    _T( "rc-file-outofdate.png" ),
    _T( "rc-file-uptodate.png" ),
    _T( "rc-file-requireslock.png" ),
    _T( "rc-file-external.png" ),
    _T( "rc-file-gotlock.png" ),
    _T( "rc-file-lockstolen.png" ),
    _T( "rc-file-mismatch.png" ),
    _T( "rc-file-noncontrolled.png" ),
    _T( "workspace.png" ),
    _T( "workspace-readonly.png" ),
    _T( "project.png" ),
    _T( "project-readonly.png" ),
    _T( "folder_open.png" ),
    _T( "vfolder_open.png" ),
    wxEmptyString
  };
  wxBitmap bmp;
  wxImageList *images = new wxImageList( 16, 16 );
  wxString prefix = ConfigManager::ReadDataPath() + _T( "/images/" );
  for( int i = 0; !imgs[i].IsEmpty(); ++i ) {
    bmp = M_Code_LoadBitmap( prefix + imgs[i], wxBITMAP_TYPE_PNG );
    images->Add( bmp );
  }
  return images;
}

int cbProjectTreeImages::WorkspaceIconIndex( bool read_only ) {
  if( read_only ) {
    return ( int )fvsWorkspaceReadOnly;
  }
  return ( int )fvsWorkspace;
}

int cbProjectTreeImages::ProjectIconIndex( bool read_only ) {
  if( read_only ) {
    return ( int )fvsProjectReadOnly;
  }
  return ( int )fvsProject;
}

int cbProjectTreeImages::FolderIconIndex() {
  return ( int )fvsFolder;
}

int cbProjectTreeImages::VirtualFolderIconIndex() {
  return ( int )fvsVirtualFolder;
}
